﻿using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Markup;
using SL5.Common;

namespace System.Windows.Data
{
	/// <summary>
	/// Enables multiple collections and items to be displayed as a single list.
	/// </summary>
	[ContentProperty("Parts")]
	public sealed class CompositeCollection : MarkupExtension
	{
		#region Private Variables 
		private readonly Collection<object> _parts;
		#endregion Private Variables 

		#region Public Properties 
		/// <summary>
		/// Gets a value indicating if the composite collection is sealed.
		/// </summary>
		internal bool IsSealed
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the component parts making up the composite collection.
		/// </summary>
		public Collection<object> Parts
		{
			get
			{
				return this._parts;
			}
		}
		#endregion Public Properties 

		#region Public Methods 
		/// <summary>
		/// When implemented in a derived class, returns an object that is provided as the value of the target property for this markup extension.
		/// </summary>
		/// <param name="serviceProvider">A service provider helper that can provide services for the markup extension.</param>
		/// <returns>The object value to set on the property where the extension is applied.</returns>
		public override object ProvideValue(IServiceProvider serviceProvider)
		{
			this.IsSealed = true;
			return new ReadOnlyObservableCollection<object>(new CompositeObservableCollection(this));
		}

		/// <summary>
		/// If the composite collection is sealed, raise an exception.
		/// </summary>
		internal void ThrowExceptionIfSealed()
		{
			if (this.IsSealed)
			{
				throw new InvalidOperationException("Can not modify this object once sealed.");
			}
		}
		#endregion Public Methods 

		#region Public Constructors 
		/// <summary>
		/// Initializes a new instance of the <see cref="CompositeCollection"/> class.
		/// </summary>
		public CompositeCollection()
		{
			this._parts = new PartsCollection(this);
		}
		#endregion Public Constructors 

		#region Nested Classes 
		/// <summary>
		/// Composite observable collection.
		/// </summary>
		private sealed class CompositeObservableCollection : ObservableCollection<object>
		{
			#region Private Variables 
			private bool _suspendCollectionChangedEvents;
			private readonly CompositeCollection _compositeCollection;
			private readonly int[] _indexMap;
			#endregion Private Variables 

			#region Private Methods 
			/// <summary>
			/// Handles the CollectionChanged event of the SourceCollection.
			/// </summary>
			/// <param name="sender">The source of the event.</param>
			/// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
			private void SourceCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
			{
				Deployment.Current.Dispatcher.CheckAccess();
				int partIndex = this._compositeCollection.Parts.FindIndex(item =>
				{
					CollectionContainer container = item as CollectionContainer;
					return container != null && Object.ReferenceEquals(container.Collection, sender);
				});
				int startIndex = this._indexMap[partIndex];
				switch (e.Action)
				{
					case NotifyCollectionChangedAction.Reset:
						this._suspendCollectionChangedEvents = true;
						try
						{
							int oldCount = (partIndex == this._indexMap.Length - 1 ? this.Count : this._indexMap[partIndex + 1]) - startIndex;
							for (int i = 0; i < oldCount; i++)
							{
								this.RemoveAt(startIndex);
							}
							int itemCount = 0;
							foreach (object item in (IEnumerable)sender)
							{
								this.Add(item);
								itemCount++;
							}
							for (int index = partIndex + 1; index < this._compositeCollection.Parts.Count; index++)
							{
								this._indexMap[index] += (itemCount - oldCount);
							}
						}
						finally
						{
							this._suspendCollectionChangedEvents = false;
							this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
						}
						break;
					case NotifyCollectionChangedAction.Add:
						for (int index = partIndex + 1; index < this._compositeCollection.Parts.Count; index++)
						{
							this._indexMap[index]++;
						}
						this.Insert(startIndex + e.NewStartingIndex, e.NewItems[0]);
						break;
					case NotifyCollectionChangedAction.Remove:
						for (int index = partIndex + 1; index < this._compositeCollection.Parts.Count; index++)
						{
							this._indexMap[index]--;
						}
						this.RemoveAt(startIndex + e.OldStartingIndex);
						break;
					case NotifyCollectionChangedAction.Replace:
						this[startIndex + e.OldStartingIndex] = e.NewItems[0];
						break;
				}
			}
			#endregion Private Methods 

			#region Protected Methods 
			/// <summary>
			/// Raises the <see cref="E:System.Collections.Specialized.INotifyCollectionChanged.CollectionChanged"/> event.
			/// </summary>
			/// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
			protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
			{
				if (!this._suspendCollectionChangedEvents)
				{
					base.OnCollectionChanged(e);
				}
			}
			#endregion Protected Methods 

			#region Public Constructors 
			/// <summary>
			/// Initializes a new instance of the <see cref="CompositeObservableCollection"/> class.
			/// </summary>
			/// <param name="compositeCollection">The composite collection.</param>
			public CompositeObservableCollection(CompositeCollection compositeCollection)
			{
				this._compositeCollection = compositeCollection;
				this._indexMap = new int[compositeCollection.Parts.Count];
				for (int index = 0; index < this._compositeCollection.Parts.Count; index++)
				{
					this._indexMap[index] = this.Count;
					object part = this._compositeCollection.Parts[index];
					CollectionContainer container = part as CollectionContainer;
					if (container != null)
					{
						if (container.Collection == null)
						{
							throw new InvalidOperationException("Can not handle CollectionContainer with a null collection.");
						}
						INotifyCollectionChanged notifier = container.Collection as INotifyCollectionChanged;
						if (notifier != null)
						{
							notifier.CollectionChanged += this.SourceCollection_CollectionChanged;
						}
						foreach (object item in container.Collection)
						{
							this.Add(item);
						}
					}
					else
					{
						this.Add(part);
					}
				}
			}
			#endregion Public Constructors 
		}

		/// <summary>
		/// Collection of parts for the composite collection
		/// </summary>
		private sealed class PartsCollection : Collection<object>
		{
			#region Private Variables 
			private readonly CompositeCollection _compositeCollection;
			#endregion Private Variables 

			#region Protected Methods 
			/// <summary>
			/// Removes all elements from the <see cref="T:System.Collections.ObjectModel.Collection`1"/>.
			/// </summary>
			protected override void ClearItems()
			{
				this._compositeCollection.ThrowExceptionIfSealed();
				base.ClearItems();
			}

			/// <summary>
			/// Inserts an element into the <see cref="T:System.Collections.ObjectModel.Collection`1"/> at the specified index.
			/// </summary>
			/// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
			/// <param name="item">The object to insert. The value can be null for reference types.</param>
			/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.
			/// -or-
			///   <paramref name="index"/> is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"/>.
			///   </exception>
			protected override void InsertItem(int index, object item)
			{
				this._compositeCollection.ThrowExceptionIfSealed();
				CollectionContainer container = item as CollectionContainer;
				if (container != null)
				{
					container.CompositeCollection = this._compositeCollection;
				}
				base.InsertItem(index, item);
			}

			/// <summary>
			/// Removes the element at the specified index of the <see cref="T:System.Collections.ObjectModel.Collection`1"/>.
			/// </summary>
			/// <param name="index">The zero-based index of the element to remove.</param>
			/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.
			/// -or-
			///   <paramref name="index"/> is equal to or greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"/>.
			///   </exception>
			protected override void RemoveItem(int index)
			{
				this._compositeCollection.ThrowExceptionIfSealed();
				CollectionContainer container = this.Items[index] as CollectionContainer;
				if (container != null)
				{
					container.CompositeCollection = null;
				}
				base.RemoveItem(index);
			}

			/// <summary>
			/// Replaces the element at the specified index.
			/// </summary>
			/// <param name="index">The zero-based index of the element to replace.</param>
			/// <param name="item">The new value for the element at the specified index. The value can be null for reference types.</param>
			/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.
			/// -or-
			///   <paramref name="index"/> is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"/>.
			///   </exception>
			protected override void SetItem(int index, object item)
			{
				this._compositeCollection.ThrowExceptionIfSealed();
				CollectionContainer container = this.Items[index] as CollectionContainer;
				if (container != null)
				{
					container.CompositeCollection = null;
				}
				if ((container = item as CollectionContainer) != null)
				{
					container.CompositeCollection = this._compositeCollection;
				}
				base.SetItem(index, item);
			}
			#endregion Protected Methods 

			#region Public Constructors 
			/// <summary>
			/// Initializes a new instance of the <see cref="PartsCollection"/> class.
			/// </summary>
			/// <param name="compositeCollection">The composite collection.</param>
			public PartsCollection(CompositeCollection compositeCollection)
			{
				this._compositeCollection = compositeCollection;
			}
			#endregion Public Constructors 
		}
		#endregion Nested Classes 
	}
}